package com.example.auth.service.impl;

import com.example.auth.dto.ProjectDTO;
import com.example.auth.dto.TaskDTO;
import com.example.auth.entity.Project;
import com.example.auth.entity.User;
import com.example.auth.entity.Task;
import com.example.auth.repository.ProjectRepository;
import com.example.auth.repository.TaskRepository;
import com.example.auth.repository.TaskSubmissionRepository;
import com.example.auth.repository.UserRepository;
import com.example.auth.repository.ProjectEvaluationRepository;
import com.example.auth.service.ProjectService;
import com.example.auth.service.TaskService;
import com.example.auth.dto.TaskRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * Project service implementation class
 */
@Service
public class ProjectServiceImpl implements ProjectService {

    private static final Logger logger = LoggerFactory.getLogger(ProjectServiceImpl.class);

    @Autowired
    private ProjectRepository projectRepository;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private TaskRepository taskRepository;

    @Autowired
    private TaskSubmissionRepository taskSubmissionRepository;

    @Autowired
    private ProjectEvaluationRepository projectEvaluationRepository;

    @Lazy
    @Autowired
    private TaskService taskService;

    /**
     * Creates a project
     */
    @Override
    public ProjectDTO createProject(ProjectDTO projectDTO) {
        logger.info("Creating project: {}", projectDTO.getTitle());

        try {
            // Convert DTO to entity
            Project project = projectDTO.toEntity();

            // Set default status
            if (project.getStatus() == null) {
                project.setStatus("Not Started");
            }

            // Save project
            Project savedProject = projectRepository.save(project);

            // Get supervisor and student names
            String supervisorName = getUserNameById(project.getSupervisorId());
            String assigneeName = getUserNameById(project.getAssigneeId());

            // Return the created project DTO
            return ProjectDTO.fromEntity(savedProject, supervisorName, assigneeName);
        } catch (Exception e) {
            logger.error("Failed to create project", e);
            throw new RuntimeException("Failed to create project: " + e.getMessage());
        }
    }

    /**
     * Updates a project
     */
    @Override
    public ProjectDTO updateProject(Long id, ProjectDTO projectDTO) {
        logger.info("Updating project, ID: {}", id);

        try {
            // Query project
            Optional<Project> projectOptional = projectRepository.findById(id);
            if (!projectOptional.isPresent()) {
                throw new RuntimeException("Project not found");
            }

            // Get existing project
            Project existingProject = projectOptional.get();

            // Check if project is in Review Rejected status
            if ("Review Rejected".equals(existingProject.getStatus())) {
                logger.warn("Cannot update project in Review Rejected status, Project ID: {}", id);
                throw new RuntimeException("Cannot update project in Review Rejected status");
            }

            // Update attributes
            Project updatedProject = projectDTO.toEntity();
            updatedProject.setId(id);

            // Keep the original creation time
            updatedProject.setCreateTime(existingProject.getCreateTime());

            // Save the updated project
            Project savedProject = projectRepository.save(updatedProject);

            // Get supervisor and student names
            String supervisorName = getUserNameById(savedProject.getSupervisorId());
            String assigneeName = getUserNameById(savedProject.getAssigneeId());

            // Return the updated project DTO
            return ProjectDTO.fromEntity(savedProject, supervisorName, assigneeName);
        } catch (Exception e) {
            logger.error("Failed to update project", e);
            throw new RuntimeException("Failed to update project: " + e.getMessage());
        }
    }

    /**
     * Gets project details
     */
    @Override
    public ProjectDTO getProjectById(Long id) {
        logger.info("Getting project details, ID: {}", id);

        try {
            // Query project
            Optional<Project> projectOptional = projectRepository.findById(id);
            if (!projectOptional.isPresent()) {
                throw new RuntimeException("Project not found");
            }

            Project project = projectOptional.get();

            // Get supervisor and student names
            String supervisorName = getUserNameById(project.getSupervisorId());
            String assigneeName = getUserNameById(project.getAssigneeId());

            // Return project DTO
            return ProjectDTO.fromEntity(project, supervisorName, assigneeName);
        } catch (Exception e) {
            logger.error("Failed to get project details", e);
            throw new RuntimeException("Failed to get project details: " + e.getMessage());
        }
    }

    /**
     * Deletes a project
     */
    @Override
    public boolean deleteProject(Long id) {
        logger.info("Deleting project, ID: {}", id);

        try {
            // Query project
            if (!projectRepository.existsById(id)) {
                logger.warn("Project not found, ID: {}", id);
                return false;
            }

            // Delete project
            projectRepository.deleteById(id);
            logger.info("Project deleted successfully, ID: {}", id);
            return true;
        } catch (Exception e) {
            logger.error("Failed to delete project", e);
            return false;
        }
    }

    /**
     * Gets all projects created by a supervisor
     */
    @Override
    public List<ProjectDTO> getProjectsBySupervisor(Long supervisorId) {
        logger.info("Getting all projects created by supervisor, Supervisor ID: {}", supervisorId);

        try {
            // Query projects
            List<Project> projects = projectRepository.findBySupervisorId(supervisorId);

            // Convert to DTO list
            return projects.stream()
                    .map(project -> {
                        String supervisorName = getUserNameById(project.getSupervisorId());
                        String assigneeName = getUserNameById(project.getAssigneeId());
                        ProjectDTO projectDTO = ProjectDTO.fromEntity(project, supervisorName, assigneeName);
                        
                        // Add task completion information
                        addTaskCompletionInfo(projectDTO, project.getId());
                        
                        return projectDTO;
                    })
                    .collect(Collectors.toList());
        } catch (Exception e) {
            logger.error("Failed to get projects created by supervisor", e);
            throw new RuntimeException("Failed to get projects created by supervisor: " + e.getMessage());
        }
    }

    /**
     * Gets projects created by a supervisor with a specified status
     */
    @Override
    public List<ProjectDTO> getProjectsBySupervisorAndStatus(Long supervisorId, String status) {
        logger.info("Getting projects created by supervisor with specified status, Supervisor ID: {}, Status: {}", supervisorId, status);

        try {
            // Query projects
            List<Project> projects = projectRepository.findBySupervisorIdAndStatus(supervisorId, status);

            // Convert to DTO list
            return projects.stream()
                    .map(project -> {
                        String supervisorName = getUserNameById(project.getSupervisorId());
                        String assigneeName = getUserNameById(project.getAssigneeId());
                        ProjectDTO projectDTO = ProjectDTO.fromEntity(project, supervisorName, assigneeName);
                        
                        // Add task completion information
                        addTaskCompletionInfo(projectDTO, project.getId());
                        
                        return projectDTO;
                    })
                    .collect(Collectors.toList());
        } catch (Exception e) {
            logger.error("Failed to get projects created by supervisor with specified status", e);
            throw new RuntimeException("Failed to get projects created by supervisor with specified status: " + e.getMessage());
        }
    }

    /**
     * Gets all projects assigned to a student
     */
    @Override
    public List<ProjectDTO> getProjectsByAssignee(Long assigneeId) {
        logger.info("Getting all projects assigned to student, Student ID: {}", assigneeId);

        try {
            // Query projects
            List<Project> projects = projectRepository.findByAssigneeId(assigneeId);

            // Convert to DTO list
            return projects.stream()
                    .map(project -> {
                        String supervisorName = getUserNameById(project.getSupervisorId());
                        String assigneeName = getUserNameById(project.getAssigneeId());
                        ProjectDTO projectDTO = ProjectDTO.fromEntity(project, supervisorName, assigneeName);
                        
                        // Add task completion information
                        addTaskCompletionInfo(projectDTO, project.getId());
                        
                        return projectDTO;
                    })
                    .collect(Collectors.toList());
        } catch (Exception e) {
            logger.error("Failed to get projects assigned to student", e);
            throw new RuntimeException("Failed to get projects assigned to student: " + e.getMessage());
        }
    }

    /**
     * Gets projects assigned to a student with a specified status
     */
    @Override
    public List<ProjectDTO> getProjectsByAssigneeAndStatus(Long assigneeId, String status) {
        logger.info("Getting projects assigned to student with specified status, Student ID: {}, Status: {}", assigneeId, status);

        try {
            // Query projects
            List<Project> projects = projectRepository.findByAssigneeIdAndStatus(assigneeId, status);

            // Convert to DTO list
            return projects.stream()
                    .map(project -> {
                        String supervisorName = getUserNameById(project.getSupervisorId());
                        String assigneeName = getUserNameById(project.getAssigneeId());
                        return ProjectDTO.fromEntity(project, supervisorName, assigneeName);
                    })
                    .collect(Collectors.toList());
        } catch (Exception e) {
            logger.error("Failed to get projects assigned to student with specified status", e);
            throw new RuntimeException("Failed to get projects assigned to student with specified status: " + e.getMessage());
        }
    }

    /**
     * Gets projects by category
     */
    @Override
    public List<ProjectDTO> getProjectsByCategory(String category) {
        logger.info("Getting projects by category, Category: {}", category);

        try {
            // Query projects
            List<Project> projects = projectRepository.findByCategory(category);

            // Convert to DTO list
            return projects.stream()
                    .map(project -> {
                        String supervisorName = getUserNameById(project.getSupervisorId());
                        String assigneeName = getUserNameById(project.getAssigneeId());
                        return ProjectDTO.fromEntity(project, supervisorName, assigneeName);
                    })
                    .collect(Collectors.toList());
        } catch (Exception e) {
            logger.error("Failed to get projects by category", e);
            throw new RuntimeException("Failed to get projects by category: " + e.getMessage());
        }
    }

    /**
     * Assigns a project to a student
     */
    @Override
    public ProjectDTO assignProjectToStudent(Long projectId, Long studentId) {
        logger.info("Assigning project to student, Project ID: {}, Student ID: {}", projectId, studentId);

        try {
            // Query project
            Optional<Project> projectOptional = projectRepository.findById(projectId);
            if (!projectOptional.isPresent()) {
                throw new RuntimeException("Project not found");
            }

            // Verify student existence
            Optional<User> studentOptional = userRepository.findById(studentId);
            if (!studentOptional.isPresent()) {
                throw new RuntimeException("Student not found");
            }

            Project project = projectOptional.get();

            // Set assigned student ID
            project.setAssigneeId(studentId);

            // If the status is 'Not Started', update it to 'In Progress'
            if ("Not Started".equals(project.getStatus())) {
                project.setStatus("In Progress");
            }

            // Save update
            Project savedProject = projectRepository.save(project);

            // Get supervisor and student names
            String supervisorName = getUserNameById(savedProject.getSupervisorId());
            String assigneeName = getUserNameById(savedProject.getAssigneeId());

            // Return the updated project DTO
            return ProjectDTO.fromEntity(savedProject, supervisorName, assigneeName);
        } catch (Exception e) {
            logger.error("Failed to assign project to student", e);
            throw new RuntimeException("Failed to assign project to student: " + e.getMessage());
        }
    }

    /**
     * Updates project status
     */
    @Override
    public ProjectDTO updateProjectStatus(Long projectId, String status) {
        logger.info("Updating project status, Project ID: {}, New Status: {}", projectId, status);

        try {
            // Query project
            Optional<Project> projectOptional = projectRepository.findById(projectId);
            if (!projectOptional.isPresent()) {
                throw new RuntimeException("Project not found");
            }

            // Verify status value
            if (!isValidStatus(status)) {
                throw new RuntimeException("Invalid status value");
            }

            Project project = projectOptional.get();

            // Update status
            project.setStatus(status);

            // If the status is 'Completed', set the end time to the current time
            if ("Completed".equals(status) && project.getEndTime() == null) {
                project.setEndTime(LocalDateTime.now());
            }

            // Save update
            Project savedProject = projectRepository.save(project);

            // Get supervisor and student names
            String supervisorName = getUserNameById(savedProject.getSupervisorId());
            String assigneeName = getUserNameById(savedProject.getAssigneeId());

            // Return the updated project DTO
            return ProjectDTO.fromEntity(savedProject, supervisorName, assigneeName);
        } catch (Exception e) {
            logger.error("Failed to update project status", e);
            throw new RuntimeException("Failed to update project status: " + e.getMessage());
        }
    }

    /**
     * Updates project status based on task completion
     */
    @Override
    @Transactional
    public ProjectDTO updateProjectStatusBasedOnTasks(Long projectId) {
        logger.info("Updating project status based on task completion, Project ID: {}", projectId);

        try {
            // Query project
            Optional<Project> projectOpt = projectRepository.findById(projectId);
            if (!projectOpt.isPresent()) {
                throw new RuntimeException("Project not found");
            }

            Project project = projectOpt.get();

            // Check if the project has tasks
            List<Task> tasks = taskRepository.findByProjectId(projectId);
            long taskCount = tasks.size();

            if (taskCount == 0) {
                // If the project has no tasks, do not change the status
                logger.info("Project has no tasks, not changing status, Project ID: {}", projectId);

                // Get supervisor and student names
                String supervisorName = getUserNameById(project.getSupervisorId());
                String assigneeName = getUserNameById(project.getAssigneeId());

                // Return project DTO
                return ProjectDTO.fromEntity(project, supervisorName, assigneeName);
            }

            // Calculate task completion status in the project
            long completedTasks = 0;          // Number of completed tasks
            long tasksWithSubmissions = 0;    // Number of tasks with submission records

            for (Task task : tasks) {
                boolean hasSubmission = taskSubmissionRepository.existsByTaskId(task.getId());
                if (hasSubmission) {
                    tasksWithSubmissions++;
                }
                
                // Check if task is completed (either marked as completed or has submissions)
                if (Boolean.TRUE.equals(task.getCompleted()) || "Completed".equals(task.getStatus()) || hasSubmission) {
                    completedTasks++;
                }
            }

            boolean allTasksCompleted = (completedTasks == taskCount);
            boolean hasAnySubmissions = (tasksWithSubmissions > 0);

            logger.info("Project ID: {}, Total tasks: {}, Completed tasks: {}, Tasks with submissions: {}, All tasks completed: {}",
                    projectId, taskCount, completedTasks, tasksWithSubmissions, allTasksCompleted);

            // Update project status based on task completion status
            String newStatus = null;
            String currentStatus = project.getStatus();

            // Check if the project has been evaluated
            boolean hasEvaluation = projectEvaluationRepository.findByProjectId(projectId).isPresent();

            if (allTasksCompleted && hasEvaluation) {
                // All tasks are completed AND project has been evaluated, set status to "Completed"
                newStatus = "Completed";
            } else if (allTasksCompleted && !hasEvaluation) {
                // All tasks are completed but NOT evaluated yet, set status to "Waiting for Review"
                newStatus = "Waiting for Review";
            } else if (hasAnySubmissions) {
                // Some tasks have submissions but not all completed, status is "In Progress"
                // Only update if current status is not "Waiting for Review", "Completed" or "Closed"
                if (!"Waiting for Review".equals(currentStatus) && 
                    !"Completed".equals(currentStatus) && 
                    !"Closed".equals(currentStatus)) {
                    newStatus = "In Progress";
                }
            } else {
                // No task submissions, status is "Not Started"
                if (!"Waiting for Review".equals(currentStatus) && 
                    !"Completed".equals(currentStatus) && 
                    !"Closed".equals(currentStatus)) {
                    newStatus = "Not Started";
                }
            }

            // Only update if the status needs to be changed
            if (newStatus != null && !newStatus.equals(currentStatus)) {
                project.setStatus(newStatus);
                project = projectRepository.save(project);
                logger.info("Project status updated from '{}' to '{}', Project ID: {}", currentStatus, newStatus, projectId);
            } else {
                logger.info("Project status remains unchanged '{}', Project ID: {}", currentStatus, projectId);
            }

            // Get supervisor and student names
            String supervisorName = getUserNameById(project.getSupervisorId());
            String assigneeName = getUserNameById(project.getAssigneeId());

            // Return the updated project DTO
            return ProjectDTO.fromEntity(project, supervisorName, assigneeName);
        } catch (Exception e) {
            logger.error("Failed to update project status based on task completion", e);
            throw new RuntimeException("Failed to update project status based on task completion: " + e.getMessage());
        }
    }

    /**
     * Student applies for a project
     */
    @Override
    public ProjectDTO applyForProject(ProjectDTO projectDTO) {
        logger.info("Student applies for project: {}", projectDTO.getTitle());

        try {
            // Convert DTO to entity
            Project project = projectDTO.toEntity();

            // Set status to 'Pending Review'
            project.setStatus("Pending Review");

            // Save project
            Project savedProject = projectRepository.save(project);

            // Get supervisor and student names
            String supervisorName = getUserNameById(project.getSupervisorId());
            String assigneeName = getUserNameById(project.getAssigneeId());

            // Check if task plan is included
            TaskRequest taskRequest = projectDTO.getTaskRequest();
            if (taskRequest != null) {
                logger.info("Project application includes task plan, creating tasks, Project ID: {}", savedProject.getId());

                // Call TaskService to create tasks
                taskService.createBatchTasksForProject(savedProject.getId(), taskRequest);
            }

            // Check if the 'tasks' array is included
            List<TaskDTO> tasks = projectDTO.getTasks();
            if (tasks != null && !tasks.isEmpty()) {
                logger.info("Project application includes 'tasks' array, creating tasks, Project ID: {}, Task Count: {}", savedProject.getId(), tasks.size());

                // Iterate and create each task
                for (TaskDTO taskDTO : tasks) {
                    // Set the task's project ID, supervisor ID, and student ID
                    taskDTO.setProjectId(savedProject.getId());

                    // Ensure the supervisor ID is set
                    if (taskDTO.getSupervisorId() == null) {
                        taskDTO.setSupervisorId(project.getSupervisorId());
                    }

                    // Set the student ID to the project applicant
                    taskDTO.setAssigneeId(project.getAssigneeId());

                    // Create task
                    try {
                        taskService.createTask(taskDTO);
                        logger.info("Successfully created task for project: ProjectID={}, TaskTitle={}", savedProject.getId(), taskDTO.getTitle());
                    } catch (Exception e) {
                        logger.error("Failed to create task for project: ProjectID={}, TaskTitle={}, Error={}",
                                savedProject.getId(), taskDTO.getTitle(), e.getMessage());
                    }
                }
            }

            // Return the created project DTO
            return ProjectDTO.fromEntity(savedProject, supervisorName, assigneeName);
        } catch (Exception e) {
            logger.error("Project application failed", e);
            throw new RuntimeException("Project application failed: " + e.getMessage());
        }
    }

    /**
     * Supervisor reviews a project application
     */
    @Override
    public ProjectDTO reviewProjectApplication(Long projectId, boolean approved) {
        logger.info("Supervisor reviewing project application, Project ID: {}, Approved: {}", projectId, approved);

        try {
            // Query project
            Optional<Project> projectOptional = projectRepository.findById(projectId);
            if (!projectOptional.isPresent()) {
                throw new RuntimeException("Project not found");
            }

            Project project = projectOptional.get();

            // Verify that the project status is 'Pending Review'
            if (!"Pending Review".equals(project.getStatus())) {
                throw new RuntimeException("Can only review projects with status 'Pending Review'");
            }

            // Set the status based on the review result
            if (approved) {
                project.setStatus("In Progress");
            } else {
                project.setStatus("Review Rejected");
            }

            // Update project
            Project savedProject = projectRepository.save(project);

            // Get supervisor and student names
            String supervisorName = getUserNameById(savedProject.getSupervisorId());
            String assigneeName = getUserNameById(savedProject.getAssigneeId());

            // Return the updated project DTO
            return ProjectDTO.fromEntity(savedProject, supervisorName, assigneeName);
        } catch (Exception e) {
            logger.error("Failed to review project application", e);
            throw new RuntimeException("Failed to review project application: " + e.getMessage());
        }
    }

    /**
     * Gets a list of pending project applications
     */
    @Override
    public List<ProjectDTO> getPendingReviewProjects(Long supervisorId) {
        logger.info("Getting pending review projects, Supervisor ID: {}", supervisorId);

        try {
            // Query projects
            List<Project> projects = projectRepository.findBySupervisorIdAndStatus(supervisorId, "Pending Review");

            // Convert to DTO list
            return projects.stream()
                    .map(project -> {
                        String supervisorName = getUserNameById(project.getSupervisorId());
                        String assigneeName = getUserNameById(project.getAssigneeId());
                        ProjectDTO projectDTO = ProjectDTO.fromEntity(project, supervisorName, assigneeName);
                        
                        // Add task completion information
                        addTaskCompletionInfo(projectDTO, project.getId());
                        
                        return projectDTO;
                    })
                    .collect(Collectors.toList());
        } catch (Exception e) {
            logger.error("Failed to get pending review projects", e);
            throw new RuntimeException("Failed to get pending review projects: " + e.getMessage());
        }
    }

    /**
     * Gets the name by user ID
     */
    private String getUserNameById(Long userId) {
        if (userId == null) {
            return null;
        }

        try {
            Optional<User> userOptional = userRepository.findById(userId);
            if (userOptional.isPresent()) {
                User user = userOptional.get();
                return user.getRealName() != null ? user.getRealName() : user.getUsername();
            }
        } catch (Exception e) {
            logger.warn("Failed to get username, ID: {}", userId, e);
        }

        return null;
    }

    /**
     * Checks if the status value is valid
     */
    private boolean isValidStatus(String status) {
        return status != null && (
                "Not Started".equals(status) ||
                        "In Progress".equals(status) ||
                        "Completed".equals(status) ||
                        "Closed".equals(status) ||
                        "Waiting for Review".equals(status) ||
                        "Pending Review".equals(status) ||
                        "Review Rejected".equals(status)
        );
    }

    /**
     * Helper method to add task completion information to a project DTO
     */
    private void addTaskCompletionInfo(ProjectDTO projectDTO, Long projectId) {
        try {
            // Get all tasks for this project
            List<Task> tasks = taskRepository.findByProjectId(projectId);
            
            // Set total task count
            projectDTO.setTotalTaskCount(tasks.size());
            
            // Count completed tasks
            long completedCount = tasks.stream()
                    .filter(Task::getCompleted)
                    .count();
            
            projectDTO.setCompletedTaskCount((int) completedCount);
            
        } catch (Exception e) {
            logger.warn("Failed to add task completion information for project ID: {}, Error: {}", 
                    projectId, e.getMessage());
            // Set default values to avoid null
            projectDTO.setTotalTaskCount(0);
            projectDTO.setCompletedTaskCount(0);
        }
    }

    /**
     * Check if a user is the supervisor of a project
     * @param projectId Project ID
     * @param userId User ID
     * @return Returns true if the user is the project supervisor; otherwise, returns false
     */
    @Override
    public boolean isUserProjectSupervisor(Long projectId, Long userId) {
        logger.info("Checking if user is project supervisor: Project ID={}, User ID={}", projectId, userId);
        
        try {
            if (projectId == null || userId == null) {
                logger.warn("Project ID or User ID is null");
                return false;
            }
            
            // Query project information
            Optional<Project> projectOptional = projectRepository.findById(projectId);
            if (!projectOptional.isPresent()) {
                logger.warn("Project does not exist: Project ID={}", projectId);
                return false;
            }
            
            Project project = projectOptional.get();
            
            // Check if the user is the supervisor of the project
            boolean isSupervisor = userId.equals(project.getSupervisorId());
            logger.info("Result of checking if user is project supervisor: {}", isSupervisor);
            
            return isSupervisor;
        } catch (Exception e) {
            logger.error("Error checking project supervisor status", e);
            return false;
        }
    }
}